/*
实现一个 MyCalendar 类来存放你的日程安排。如果要添加的日程安排不会造成 重复预订 ，则可以存储这个新的日程安排。

当两个日程安排有一些时间上的交叉时（例如两个日程安排都在同一时间内），就会产生 重复预订 。

日程可以用一对整数 start 和 end 表示，这里的时间是半开区间，即 [start, end), 实数 x 的范围为，  start <= x < end 。

实现 MyCalendar 类：

MyCalendar() 初始化日历对象。
boolean book(int start, int end) 如果可以将日程安排成功添加到日历中而不会导致重复预订，返回 true 。否则，返回 false 并且不要将该日程安排添加到日历中。
 

示例：

输入：
["MyCalendar", "book", "book", "book"]
[[], [10, 20], [15, 25], [20, 30]]
输出：
[null, true, false, true]

解释：
MyCalendar myCalendar = new MyCalendar();
myCalendar.book(10, 20); // return True
myCalendar.book(15, 25); // return False ，这个日程安排不能添加到日历中，因为时间 15 已经被另一个日程安排预订了。
myCalendar.book(20, 30); // return True ，这个日程安排可以添加到日历中，因为第一个日程安排预订的每个时间都小于 20 ，且不包含时间 20 。
 

提示：

0 <= start < end <= 109
每个测试用例，调用 book 方法的次数最多不超过 1000 次。

来源：力扣（LeetCode）
链接：https://leetcode-cn.com/problems/my-calendar-i
著作权归领扣网络所有。商业转载请联系官方授权，非商业转载请注明出处。
*/

#include "../stdc++.h"

// 边界计数
class MyCalendar {
public:
    MyCalendar() {}

    bool book(int start, int end) {
        // 在遍历的时候 start 和 end 的两个值就相互抵消了
        record[start] += 1;
        record[end] -= 1;
        int cur = 0;
        for (auto& p : record) {
            cur += p.second;
            if (cur > 1) {
                record[start]--;
                record[end]++;
                return false;
            }
        }
        return true;
    }
private:
    map<int, int> record; // 已经预定好的日程
};

// 直接遍历
class MyCalendar {
public:
    MyCalendar() {

    }
    
    bool book(int start, int end) {
        for (auto& time : record) {
            if ((start < time.first && end > time.second) ||
                (start >= time.first && start < time.second) ||
                (end > time.first && end <= time.second))
            {
                return false;
            }
        }
        record.emplace_back(start, end);
        return true;
    }
private:
    vector<pair<int, int>> record;
};

// 二分查找
class MyCalendar {
public:
    bool book(int start, int end) {
        // 每次查找起点大于等于 end 的第一个区间 [l1,r1)
        auto it = booked.lower_bound({end, 0});
        // 同时紧挨着 [l1,r1) 的前一个区间为 [l2,r2)，此时如果满足 r2 ≤ start < end ≤ l1
        if (it == booked.begin() || (--it)->second <= start) {
            booked.emplace(start, end);
            return true;
        }
        return false;
    }
private:
    set<pair<int, int>> booked;
};

// 线段树
class MyCalendar {
public:
    bool book(int start, int end) {
        if (query(start, end - 1, 0, 1e9, 1)) {
            return false;
        }
        update(start, end - 1, 0, 1e9, 1);
        return true;
    }
private:
    bool query(int start, int end, int left, int right, int index) {
        if (start > right || end < left) {
            return false;
        }
        // 如果该区间已被预订，则直接返回
        if (lazy.count(index)) {
            return true;
        }
        if (start <= left && end >= right) {
            return tree.count(index);
        }
        int mid = (left + right) >> 1;
        return query(start, end, left, mid, 2 * index) || query(start, end, mid + 1, right, 2 * index + 1);
    }
    void update(int start, int end, int left, int right, int index) {
        if (start > right || end < left) {
            return;
        }
        if (start <= left && end >= right) {
            tree.emplace(index);
            lazy.emplace(index);
        } else {
            int mid = (left + right) >> 1;
            update(start, end, left, mid, 2 * index);
            update(start, end, mid + 1, right, 2 * index + 1);
            tree.emplace(index);
            if (lazy.count(2 * index) && lazy.count(2 * index + 1)) {
                lazy.emplace(index);
            }
        }
    }
private:
    unordered_set<int> tree;
    unordered_set<int> lazy;
};
